fix: manage RCP source-match table so association/indirect poll ACKs carry Frame Pending - #126
fix: manage RCP source-match table so association/indirect poll ACKs carry Frame Pending#126tostmann wants to merge 1 commit into
Conversation
…carry Frame Pending The driver never touched the RCP source-match (pending) table and relied on the RCP's default Frame Pending behaviour for the auto-ACK it emits to a device's data request poll. That default is not portable across RCP vendors. On the ESP32 OpenThread RCP this breaks joining: the stack reset the driver performs during `start` puts the radio into the enhanced auto-pending mode with an empty source-match table, so every poll ACK carries Frame Pending = 0. A joining device reads that as "no data pending" after its association data request, abandons the association before adopting the short address assigned in the association response, and never receives the transport key. The same empty table also suppresses Frame Pending for sleepy end devices with queued data. Manage the table explicitly instead. `updateSrcMatchTable` rebuilds the set of extended addresses with MAC-layer data pending -- devices awaiting an association response (`pendingAssociations`) plus devices with a non-empty indirect-transmission queue -- and writes it via MAC_SRC_MATCH_EXTENDED_ADDRESSES. The MAC handler signals membership changes through a new `onSrcMatchUpdate` callback when a pending association is added/removed and when an indirect queue becomes non-empty/empty. MAC_SRC_MATCH_ENABLED is set during `formNetwork`. The host cannot set Frame Pending per-poll because the hardware auto-ACK precedes host handling, so pre-loading the table is the only portable mechanism.
|
Will require extensive testing across chips. This might not be supported in some firmware, and the behavior, if already implement on stack side, could be a problem as well if it doesn't handover control properly (or at all). This might also fix some issues we've seen with TI stack (cc: @chris-1243) |
|
Update from our side: we're stepping back from evaluating zigbee-on-host as a path to a comprehensive coordinator, so we won't be driving this PR further. The full rationale with bench evidence is in discussion #94 — short version: the failure that's genuinely Espressif-RCP-specific is one class, but a second, host-side trust-center / TCLK leave–rejoin loop reproduced on an EFR32 RCP as well, so "comprehensively functional" wasn't reachable for strict Zigbee-3.0 joiners even on a third-party radio. On this change specifically: it does what it claims on the bench — with the joiner's EUI64 in the RCP source-match table, the association / indirect-poll ACK carries Frame Pending = 1, and a fresh device joins and completes its interview. But your concerns are the right ones, and they're exactly the work we won't be carrying: it needs the cross-chip testing, and it should be gated behind feature detection, since firmwares that already manage frame-pending on the stack side could fight it. So please treat this as available rather than actively maintained — feel free to take it forward (the TI-stack angle you mentioned sounds like the more interesting lead) or close it, whatever fits the project. Thanks for the look. |
Problem
The driver never wrote the RCP source-match (pending) table and relied on the RCP's default Frame Pending behaviour for the hardware auto-ACK it emits in response to a device's MAC data request poll. That default is not portable across RCP vendors/firmware.
On the ESP32 OpenThread RCP this prevents devices from joining. The stack reset the driver performs during
start()leaves the radio in the enhanced auto-pending mode with an empty source-match table, so every poll ACK carries Frame Pending = 0. A joining device:MAC DATA_RQ) for the association response,The same empty table also suppresses Frame Pending for sleepy end devices that have queued indirect data.
This matches the ESP32 reports in the firmware-stability discussion (devices repeatedly failing to join / "transport key never acknowledged" on ESP32-C6
ot_rcp).Root cause
The host cannot set the Frame Pending bit per-poll: the RCP hardware auto-ACK is generated before the poll is handed to the host. The only portable mechanism is to pre-load the RCP source-match table with the extended addresses that currently have data pending, and let the radio set Frame Pending from a table lookup.
Fix
OTRCPDriver.updateSrcMatchTable()rebuilds the set of extended addresses with MAC-layer data pending — devices awaiting an association response (pendingAssociations) plus devices with a non-empty indirect-transmission queue (indirectTransmissions) — and writes it viaMAC_SRC_MATCH_EXTENDED_ADDRESSES.The MAC handler signals membership changes through a new
onSrcMatchUpdatecallback:processAssocReq) / removed (processDataReq),sendFrame) / empty (processDataReq).MAC_SRC_MATCH_ENABLEDis set once informNetwork. Rebuilding the full set on each change keeps it correct for concurrent joins and mirrors the existingframePending = !!indirectTransmissions.get(x)?.lengthpredicate the stack already uses on outgoing frames, so behaviour stays consistent (and now portable) across RCPs.Why this does not regress other RCPs
Enabling source-match changes the poll-ACK Frame Pending semantics for all polls, so the table must reflect all pending data, not just associations — hence both
pendingAssociationsand non-emptyindirectTransmissionsfeed it. On an RCP that previously defaulted to Frame Pending = 1, devices now correctly get 0 only when nothing is queued for them; when data is queued they still get 1.Validation
Built (
tsc), linted (biome), full test suite green (incl. updated fixtures + 2 new unit tests forupdateSrcMatchTable).Validated on hardware with an ESP32-C6
ot_rcpRCP:Validated on an ESP32-C6 RCP only (no TI/Silabs RCP on hand to exercise the no-regression path; the reasoning above is unchanged for those, but is not hardware-confirmed here).
Note: the Frame Pending bit could not be captured on-air directly (the available 802.15.4 sniffer drops ACK frames), but the behavioural change is unambiguous — the same device on the same RCP transitions from an endless re-association loop to a completed join solely with this change.
Notes
There is also an upstream ESP-IDF defect behind the "enhanced mode after reset" behaviour (
otPlatRadioEnableSrcMatchignoring itsaEnableargument); that will be reported separately. This change makes zigbee-on-host correct and portable regardless of that firmware fix.